// Tierless
// Copyright © 2010 Alphora
// This file is licensed under a modified BSD-license (see tierless_license.txt).
using System.Collections.Generic;

using Tierless.Framework.Entities;
using Tierless.Framework.EntityTypes;

namespace Tierless.Framework.Data
{
	/// <summary>
	/// Represents a dictionary of entity sets by entity set name.
	/// </summary>
	public class EntitySets : Dictionary<string, EntitySet>
	{
		/// <summary>
		/// Includes the given entity set in the dictionary, merging any entities if necessary.
		/// </summary>
		/// <param name="entitySetName">The name of the entity set to be included.</param>
		/// <param name="entitySet">The entity set to be included.</param>
		/// <remarks>
		/// If the dictionary already contains an entity set with the given name,
		/// the given entity set is merged in to the existing set using <see cref="EntitySet.Merge"/>.
		/// Otherwise, the entity set is added to the dictionary.
		/// </remarks>
		public void Include(string entitySetName, EntitySet entitySet)
		{
			EntitySet existingSet;
			if (TryGetValue(entitySetName, out existingSet))
				existingSet.Merge(entitySet);
			else
				Add(entitySetName, entitySet);
		}

		/// <summary>
		/// Merges the entity sets from the given dictionary in to this one.
		/// </summary>
		/// <param name="merge">The EntitySets instance containing the entity sets to be merged.</param>
		public void Merge(EntitySets merge)
		{
			foreach (var item in merge)
			{
				EntitySet entry;
				if (TryGetValue(item.Key, out entry))
					entry.Merge(item.Value);
				else
					Add(item.Key, item.Value);
			}
		}

		/// <summary>
		/// Replaces the entity sets in this dictionary with the ones in the given dictionary.
		/// </summary>
		/// <param name="replace">The entity sets to replace.</param>
		/// <remarks>
		/// Note that this call will not remove all entity sets from the current dictionary; it
		/// is equivalent to calling <see cref="EntitySet.Replace"/> for each entity set from the
		/// given dictionary that is currently in this dictionary, and then adding any entity sets
		/// in the given dictionary that are not in this dictionary.
		/// </remarks>
		public void Replace(EntitySets replace)
		{
			foreach (var item in replace)
			{
				EntitySet entry;
				if (TryGetValue(item.Key, out entry))
					entry.Replace(item.Value);
				else
					Add(item.Key, item.Value);
			}
		}

		/// <summary>
		/// Gets the entity set name for the given entity.
		/// </summary>
		/// <param name="entity">The entity for which the entity set name is to be determined.</param>
		/// <returns>The entity set name for the given entity.</returns>
		public string GetEntitySetName(Entity entity)
		{
			return entity.EntityTypeName;
		}

		/// <summary>
		/// Returns true if the dictionary contains an entity set that contains the given entity by ID, false otherwise.
		/// </summary>
		/// <param name="entity">The entity to be tested.</param>
		/// <returns>True if the dictionary contains an entity set that contains an entity with the same ID as the given entity, false otherwise.</returns>
		public bool Contains(Entity entity)
		{
			EntitySet entitySet;
			return 
				TryGetValue(GetEntitySetName(entity), out entitySet) 
					&& entitySet.ContainsKey(entity.ID);
		}

		/// <summary>
		/// Adds the given entity to the appropriate entity set within the dictionary.
		/// </summary>
		/// <param name="entity">The entity to be added.</param>
		/// <remarks>
		/// If the dictionary does not already contain an entity set of the appropriate type for the given
		/// entity, one will be created and added to the dictionary to contain the given entity.
		/// </remarks>
		public void Add(Entity entity)
		{
			var entitySetName = GetEntitySetName(entity);
			EntitySet entitySet;
			if (!TryGetValue(entitySetName, out entitySet))
			{
				entitySet = new EntitySet(entity.EntityTypeName);
				Add(entitySetName, entitySet);
			}

			entitySet.Add(entity.ID, entity);
		}

		/// <summary>
		/// Ensures that the entity sets within the dictionary contain all the entities referenced by each entity in the dictionary.
		/// </summary>
		/// <remarks>
		/// This method is used to ensure that the dictionary directly references all the entity types within the entities contained
		/// in the dictionary. For example, if the dictionary contains an entity set containing an entity with child entities, then
		/// after this call, the dictionary will contain entity sets for both the original entity set, as well as the entity type of the
		/// child entities, with direct references to all the child entities. This is useful for ensuring that searches within the
		/// entity set do not have to traverse the entity graph for each entity in each entity set in the dictionary.
		/// </remarks>
		public void Normalize()
		{
			var toVisit = new EntityList();
			foreach (var item in this)
				foreach (var entity in item.Value)
					toVisit.Add(entity);

			while (toVisit.Count > 0)
			{
				var entity = toVisit[0];
				toVisit.RemoveAt(0);

				foreach (var reference in entity.EntityType.References)
				{
					if (reference.Kind == ReferenceKind.Lookup)
					{
						var lookupEntity = entity.Lookups[reference.IndexName];
						if ((lookupEntity != null) && !Contains(lookupEntity))
						{
							Add(lookupEntity);
							toVisit.Add(lookupEntity);
						}
					}
					else
					{
						foreach (var childEntity in entity.Children[reference.IndexName])
						{
							if (!Contains(childEntity))
							{
								Add(childEntity);
								toVisit.Add(childEntity);
							}
						}
					}
				}
			}
		}
	}
}